package se.webbzon.oschi01.sound;

import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;

import se.webbzon.oschi01.stream.LittleEndianDataInputStream;

public class MemoryWavSource implements ClipSource, DataLineSource {
	
	// The audio format of this WavSource
	private final AudioFormat af;
	
	// The audio data of this WavSource
	private final byte[] data;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new WavSource by reading the audio format and data
	 * from the provided input stream. (Not working on Java 7 update 51
	 * when running from JAR) **/
	public MemoryWavSource(InputStream is) throws IOException {
		byte[] data = null;
		AudioFormat af = null;
		
		// RIFF chunk
		DataInput ledis = new LittleEndianDataInputStream(is);
		byte[] chunkId = new byte[4];
		ledis.readFully(chunkId, 0, 4);
		if (!dataCompare(chunkId,"riff"))
			throw new IOException("Only RIFF-files are supported!");
			
		ledis.readInt(); // int chunkSize = 
		byte[] format = new byte[4];
		ledis.readFully(format, 0, 4);
		if (!dataCompare(format,"wave"))
			throw new IOException("Only WAVE-format is supported!");
			
		try {
			while (true) {
				byte[] subChunkId = new byte[4];
				ledis.readFully(subChunkId, 0, 4);
				int subChunkSize = ledis.readInt(); 
				
				if (dataCompare(subChunkId,"fmt ")) {
					// FMT chunk
					short audioFormat = ledis.readShort();
					if (audioFormat != 1)
						throw new IOException("Only PCM sound format is supported!");
					short channels = ledis.readShort();
					int sampleRate = ledis.readInt();
					ledis.readInt(); // int byteRate = 
					ledis.readShort(); // short blockAlign = 
					short bitsPerSample = ledis.readShort();
					if (subChunkSize > 16)
						ledis.skipBytes(subChunkSize-16);
					af = new AudioFormat(	(float) sampleRate, bitsPerSample,
							channels,bitsPerSample != 8,false);
					
				} else if (dataCompare(subChunkId,"data")) {
					// Data chunk
					data = new byte[subChunkSize];
					ledis.readFully(data,0,subChunkSize);
					
				} else if (subChunkSize > 0)
					ledis.skipBytes(subChunkSize);
				
				// Break if audio format and data has been read
				if (af != null && data != null)
					break;
			}
			
		} catch (EOFException e) {
			// The end of the stream is reached
			if (data == null || af == null)
				throw new IOException("Unsupported sound format!");
		}
		
		this.data = data;
		this.af = af;
	}
	
	@Override public AudioFormat getFormat() {
		return af;
	}

	@Override public InputStream createInputStream() {
		return new ByteArrayInputStream(data);
	}

	@Override public int getSize() {
		return data.length;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Creates a new WavSource from an URL. **/
	public static final MemoryWavSource createSource(URL url) throws IOException {
		InputStream is = url.openStream();
		MemoryWavSource source = new MemoryWavSource(is);
		is.close();
		return source;
	}

	@Override public void setupClip(Clip clip) throws IOException, LineUnavailableException {
		clip.open(af, data, 0, data.length);
	}

	@Override public FiniteSound createSound() throws SoundException {
		try {
			return new DataLineSound(this);
			//return new ClipSound(this);
		} catch (LineUnavailableException | IOException e) {
			throw new SoundException(e);
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	private boolean dataCompare(byte[] data, String str) {
		try {
			String d = new String(data,"UTF-8");
			return str.toLowerCase().equals(d.toLowerCase());
		} catch (UnsupportedEncodingException e) {
			return false;
		}
	}

}
